/*
 * User: user
 * Date: 27/06/2008
 * Time: 12:35:47
 */
package org.conann.scan;

import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import org.conann.configuration.Configuration;
import org.conann.container.BeansRegistry;
import org.conann.exceptions.WebBeansSystemException;
import org.conann.metadata.AnnotationType;
import static org.conann.metadata.AnnotationType.getAnnotationType;
import static org.conann.util.ReflectionUtil.resolveClass;
import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;

import javax.webbeans.Produces;
import javax.webbeans.Specializes;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

public class ComponentScanner {

    private static final Set<Class<? extends Annotation>> RELEVANT_ANNOTATION_TYPES = Sets.newHashSet(Produces.class, Specializes.class);

    private final BeansRegistry beansRegistry;
    private final Set<String> scannedPackages = Sets.newHashSet();
    private final Set<String> ignoredPackages = Sets.newHashSet();

    public ComponentScanner(Configuration configuration) {
        beansRegistry = new BeansRegistry(configuration);
    }

    public void addToScannedPackages(String... packages) {
        scannedPackages.addAll(Arrays.asList(packages));
    }

    public void addToIgnoredPackages(String... packages) {
        ignoredPackages.addAll(Arrays.asList(packages));
    }

    public void scanSimpleComponents() {
        Multimap<Class<? extends Annotation>, String> annotationIndex = getAnnotationIndex();
        for (final Class<? extends Annotation> annotationType : annotationIndex.keySet()) {
            // Find out whether Web Beans container is interested in this annotation:
            if (getAnnotationType(annotationType) != null) {
                for (final String annotatedTypeName : annotationIndex.get(annotationType)) {
                    if (shouldBeScanned(annotatedTypeName)) {
                        Class<?> componentType = resolveClass(annotatedTypeName);
                        if (!Modifier.isAbstract(componentType.getModifiers()) && !beansRegistry.contains(componentType)) {
                            beansRegistry.addSimpleComponent(componentType);
                        }
                    }
                }
            }
        }
    }

    private Multimap<Class<? extends Annotation>, String> getAnnotationIndex() {
        AnnotationDB annotationDB = new AnnotationDB();
        annotationDB.setScanFieldAnnotations(true);
        annotationDB.setScanClassAnnotations(true);
        annotationDB.setScanMethodAnnotations(true);
        annotationDB.setScanParameterAnnotations(true);

        try {
            URL[] urls = ClasspathUrlFinder.findClassPaths();
            annotationDB.scanArchives(urls);
        } catch (IOException e) {
            throw new WebBeansSystemException(e, "Couldn't scan the classpath for components.");
        } catch (RuntimeException e) {
            throw new WebBeansSystemException(e, "Couldn't scan the classpath for components.");
        }

        Multimap<Class<? extends Annotation>, String> loadedAnnotationIndex = Multimaps.newHashMultimap();
        for (Map.Entry<String, Set<String>> entry : annotationDB.getAnnotationIndex().entrySet()) {
            Class<? extends Annotation> annotationType = (Class<? extends Annotation>) resolveClass(entry.getKey());
            if (isRelevant(annotationType)) {
                for (String className : entry.getValue()) {
                    loadedAnnotationIndex.put(annotationType, className);
                }
            }
        }
        return loadedAnnotationIndex;
    }

    private boolean isRelevant(Class<? extends Annotation> annotationType) {
        return AnnotationType.getAnnotationType(annotationType) != null || RELEVANT_ANNOTATION_TYPES.contains(annotationType);
    }

    private boolean shouldBeScanned(String annotatedTypeName) {
        return isInScannedPackages(annotatedTypeName) && !isInignoredPackages(annotatedTypeName);
    }

    private boolean isInScannedPackages(String annotatedTypeName) {
        for (String scannedPackage : scannedPackages) {
            if (annotatedTypeName.startsWith(scannedPackage)) {
                return true;
            }
        }
        return false;
    }

    private boolean isInignoredPackages(String annotatedTypeName) {
        for (String ignoredPackage : ignoredPackages) {
            if (annotatedTypeName.startsWith(ignoredPackage)) {
                return true;
            }
        }
        return false;
    }

    public BeansRegistry getComponentsRegistry() {
        return beansRegistry;
    }
}